package com.wholeintech.ble;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.wholeintech.zigbee.gw.DeviceManager;
import com.wholeintech.utils.CommonUtil;
import com.wholeintech.entity.DeviceDb;

import org.xutils.DbManager;
import org.xutils.ex.DbException;

import java.util.UUID;
import java.util.Vector;

import static com.wholeintech.ble.DeviceList.TYPE_AIR_RADIO;
import static com.wholeintech.ble.DeviceList.TYPE_TOPBOX;
import static com.wholeintech.utils.CommonIntent.ACTION_DEV_DEVCHANAGE;

/**
 * Created by Lyle on 2016-04-20.
 */
public abstract class BluetoothManage {
    private final static String TAG = "BluetoothManage";

    private final static String UUID_KEY_DATA = "0000ffe1-0000-1000-8000-00805f9b34fb";

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    public static final UUID TX_POWER_UUID = UUID.fromString("00001804-0000-1000-8000-00805f9b34fb");
    public static final UUID TX_POWER_LEVEL_UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb");
    public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
    public static final UUID FIRMWARE_REVISON_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
    public static final UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");

    public static final UUID RX_SERVICE_UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
    public static final UUID RX_CHAR_UUID = UUID.fromString("6e402c00-b5a3-f393-e0a9-e50e24dcca9e");
    public static final UUID TX_CHAR_UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");

    public static final UUID READ_SENSOR_CMD_UUID = UUID.fromString("6e4002c01-b5a3-f393-e0a9-e50e24dcca9e");

    public static final UUID AIR_RADIO_RX_SERVICE_UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");

    public static final UUID AIR_RADIO_RX_CHAR_UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
    public static final UUID AIR_RADIO_TX_CHAR_UUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");

    public static final UUID AOMEI_RADAR_SERVICE_UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
    public static final UUID AOMEI_RADAR_RX_CHAR_UUID = UUID.fromString("6e402c00-b5a3-f393-e0a9-e50e24dcca9e");
    public static final UUID AOMEI_RADAR_TX_CHAR_UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
    //public static final UUID RX_SERVICE_UUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb");
    //public static final UUID TX_CHAR_UUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
    //public static final UUID RX_CHAR_UUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");

    //public static final UUID RX_SERVICE_UUID = UUID.fromString("00000001-0000-1000-8000-e50e24dcca9e");
    // public static final UUID RX_CHAR_UUID = UUID.fromString("00000002-0000-1000-8000-e50e24dcca9e");
    // public static final UUID TX_CHAR_UUID = UUID.fromString("00000003-0000-1000-8000-e50e24dcca9e");

    //搜索BLE终端
    private BluetoothAdapter mBluetoothAdapter;

    private Handler mHandler = new Handler();
    private Context mContext;

    Vector<DevInfoItem> mDevInfoItem;


    private BluetoothManage() {
    }

    public BluetoothManage(Context c, Vector<DevInfoItem> d) {
        mContext = c;
        mDevInfoItem = d;
    }

    public boolean initialize() {

        final BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);

        if (bluetoothManager != null) {
            Log.e(TAG, "initialize - BT ");
            mBluetoothAdapter = bluetoothManager.getAdapter();

            // Checks if Bluetooth is supported on the device.
            if (mBluetoothAdapter == null) {
                Log.e(TAG, "Create ble adapter fail");
                return false;
            }

            mBluetoothAdapter.enable();

            if (!mBluetoothAdapter.isEnabled()) {
                Log.e(TAG, "onResume - BT not enabled yet");
                //Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                //startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
            }

            scanLeDevice(true);

        } else {
            Log.e(TAG, "Not found Bluetooth Service");
        }
        return true;
    }

    int scanLeDeviceFlag = 0;

    public void scanLeDevice(final boolean enable) {
        if (mBluetoothAdapter == null)
            return;
        try {
            if (enable) {

                mBluetoothAdapter.startDiscovery();//startLeScan(mLeScanCallback);
                BluetoothLeScanner mBleScanner = mBluetoothAdapter.getBluetoothLeScanner();
                if (mBleScanner != null) {
                    mBleScanner.startScan(mLeScanCallback);
                    scanLeDeviceFlag = 1;

                }
            } else {
                BluetoothLeScanner mBleScanner = mBluetoothAdapter.getBluetoothLeScanner();
                mBleScanner.stopScan(mLeScanCallback);
                mBluetoothAdapter.cancelDiscovery();
                scanLeDeviceFlag = 0;
            }
        } catch (Exception e) {
            scanLeDeviceFlag = 0;
            Log.e(TAG, "scanLeDevice:" + e.toString());
        }
    }

    ScanCallback mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);

            //Log.i(TAG, "MAC:" + result.getDevice().getAddress() + ",Rssi:" + result.getRssi() + ",ScanRecord:" + Utils.bytesToHexString(result.getScanRecord().getBytes()));

            checkDoorStateBackCall(result.getDevice().getAddress().toUpperCase(), Utils.bytesToHexString(result.getScanRecord().getBytes()).toUpperCase());
            findTopBoxAddrBackCall(result.getDevice().getAddress().toUpperCase());
        }
    };

    int GetScanCallback() {
        return scanLeDeviceFlag;
    }

    // Implements callback methods for GATT events that the app cares about.  For example,
    // connection change and services discovered.
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;

            if (newState == BluetoothProfile.STATE_CONNECTED) {

                String address = gatt.getDevice().getAddress().replace(":", "");

                for (int i = 0; i < mDevInfoItem.size(); i++) {
                    if (address.equals(mDevInfoItem.get(i).getDevMAC())) {

                        changeDevState(address, DeviceManager.Dev_STATUS_eConnected);
                        mDevInfoItem.get(i).setState(STATE_CONNECTED);
                        boolean dis = mDevInfoItem.get(i).getBtGatt().discoverServices();

                        Log.i(TAG, "BT connect completed:" + address);
                        Log.i(TAG, "Attempting to start service discovery:" + dis);
                        break;
                    }
                }

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {

                String address = gatt.getDevice().getAddress().replace(":", "");

                for (int i = 0; i < mDevInfoItem.size(); i++) {
                    if (address.equals(mDevInfoItem.get(i).getDevMAC())) {
                        changeDevState(address, DeviceManager.Dev_STATUS_eDisconnect);
                        mDevInfoItem.get(i).setState(STATE_DISCONNECTED);
                        Log.i(TAG, "BT Disconnected:" + address);
                        break;
                    }
                }
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //Log.i(TAG, "onServicesDiscovered");

                String address = gatt.getDevice().getAddress().replace(":", "");

                for (int i = 0; i < mDevInfoItem.size(); i++) {
                    //  Log.i(TAG, "DevType:" + mDevInfoItem.get(i).getDevType() + "," +mDevInfoItem.get(i).getDevMAC());

                    if (address.equals(mDevInfoItem.get(i).getDevMAC()) && (mDevInfoItem.get(i).getDevType() == TYPE_TOPBOX)) {
                        enableTXNotificationTobBox(mDevInfoItem.get(i).getBtGatt(), address);
                        Log.i(TAG, "enableTXNotificationTobBox");
                        break;
                    } else if (address.equals(mDevInfoItem.get(i).getDevMAC()) && (mDevInfoItem.get(i).getDevType() == TYPE_AIR_RADIO)) {
                        enableTXNotificationAirRadio(mDevInfoItem.get(i).getBtGatt(), address);
                        Log.i(TAG, "enableTXNotificationAirRadio");
                        break;
                    }
                }
            } else {
                Log.i(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {

                if (TX_CHAR_UUID.equals(characteristic.getUuid())) {

                    //Log.d(TAG, String.format("Received TX: %d",characteristic.getValue() ));
                    Log.e(TAG, "onCharacteristicRead:" + characteristic.getValue());

                }
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {

            if (TX_CHAR_UUID.equals(characteristic.getUuid())) {

                // Log.d(TAG, String.format("Received TX: %d",characteristic.getValue() ));
                //Log.e(TAG, "onCharacteristicChanged:" + CommonUtil.bytesToHexString(characteristic.getValue(), characteristic.getValue().length));
                getTopBoxSensorData(gatt.getDevice().getAddress(), characteristic.getValue());

            } else if (AIR_RADIO_TX_CHAR_UUID.equals(characteristic.getUuid())) {

                //Log.e(TAG, "onCharacteristicChanged:" + CommonUtil.bytesToHexString(characteristic.getValue(), characteristic.getValue().length));
                getAirRadioSensorData(gatt.getDevice().getAddress(), characteristic.getValue());

            }

            //broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }
    };

    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
    }

    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);

        // This is handling for the notification on TX Character of NUS service
        if (TX_CHAR_UUID.equals(characteristic.getUuid())) {

            // Log.d(TAG, String.format("Received TX: %d",characteristic.getValue() ));
            //intent.putExtra(EXTRA_DATA, characteristic.getValue());
        } else {

        }
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
    }

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     * @return Return true if the connection is initiated successfully. The connection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public boolean connect(final String address) {

        long timestamp = System.currentTimeMillis();
        String devMac = address.replace(":", "");
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        for (int i = 0; i < mDevInfoItem.size(); i++) {
            if (devMac.equals(mDevInfoItem.get(i).getDevMAC())) {

                BluetoothGatt mBtGatt = mDevInfoItem.get(i).getBtGatt();
                if (mBtGatt != null) {

                    if (mBtGatt.connect()) {
                        Log.i(TAG, "mBtgatt existing try disc");
                        mDevInfoItem.get(i).setState(STATE_CONNECTED);
                        return true;
                    } else {

                        if (mDevInfoItem.get(i).getTimestamp() - timestamp > 5000) {
                            Log.i(TAG, " connect timeout ");
                            mDevInfoItem.get(i).getBtGatt().disconnect();
                            mDevInfoItem.get(i).setBtGatt(null);
                            mDevInfoItem.get(i).setTimestamp(timestamp);

                        } else {
                            Log.i(TAG, " connect trying ");
                            return false;
                        }
                    }
                }
                break;
            }
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.w(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        // We want to directly connect to the device, so we are setting the autoConnect
        // parameter to false.
        //mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
        //mBluetoothDeviceAddress = address;
        //mConnectionState = STATE_CONNECTING;

        BluetoothGatt mBtGatt = device.connectGatt(mContext, false, mGattCallback);

        for (int i = 0; i < mDevInfoItem.size(); i++) {
            if (devMac.equals(mDevInfoItem.get(i).getDevMAC())) {
                mDevInfoItem.get(i).setBtGatt(mBtGatt);
                mDevInfoItem.get(i).setState(STATE_CONNECTED);
                mDevInfoItem.get(i).setTimestamp(timestamp);
                Log.i(TAG, "Trying to create a new connection," + address);
                break;
            }
        }

        return true;
    }

    /**
     * Disconnects an existing connection or cancel a pending connection. The disconnection result
     * is reported asynchronously through the
     * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
     * callback.
     */
    public void disconnect(String address) {
        /*if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.disconnect();*/

        for (int i = 0; i < mDevInfoItem.size(); i++) {
            if (address.equals(mDevInfoItem.get(i).getDevMAC())) {

                mDevInfoItem.get(i).getBtGatt().disconnect();
                break;
            }
        }
    }

    /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    public void close(String address) {
        /*if (mBluetoothGatt == null) {
            return;
        }
        Log.w(TAG, "mBluetoothGatt closed");
        mBluetoothDeviceAddress = null;
        mBluetoothGatt.close();
        mBluetoothGatt = null;*/

        for (int i = 0; i < mDevInfoItem.size(); i++) {
            if (address.equals(mDevInfoItem.get(i).getDevMAC())) {

                mDevInfoItem.get(i).getBtGatt().close();
                mDevInfoItem.get(i).setBtGatt(null);
                //mDevInfoItem.get(i).setState(STATE_CONNECTING);
                Log.i(TAG, "mBluetoothGatt closed:" + address);
                break;
            }
        }
    }

    public void cleanAll() {
        /*if (mBluetoothGatt == null) {
            return;
        }
        Log.w(TAG, "mBluetoothGatt closed");
        mBluetoothDeviceAddress = null;
        mBluetoothGatt.close();
        mBluetoothGatt = null;*/

        for (int i = 0; i < mDevInfoItem.size(); i++) {
            {
                //mDevInfoItem.get(i).getBtGatt().close();
                mDevInfoItem.get(i).setBtGatt(null);

            }
        }
    }

    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
     * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
     * callback.
     *
     * @param characteristic The characteristic to read from.
     */
   /* public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);
    }*/

    /**
     * Enables or disables notification on a give characteristic.
     *

     */

    /**
     * Enable Notification on TX characteristic
     *
     * @return
     */
    public void enableTXNotificationTobBox(BluetoothGatt mBtGatt, String address) {
        showMessage("enableTXNotificationTobBox");
        if (mBtGatt == null) {
            showMessage("BluetoothGatt is null," + mBtGatt);
            disconnect(address);
            return;
        }

        BluetoothGattService RxService = mBtGatt.getService(RX_SERVICE_UUID);
        if (RxService == null) {
            showMessage("Rx service not found!");
            disconnect(address);
            return;
        }
        BluetoothGattCharacteristic TxChar = RxService.getCharacteristic(TX_CHAR_UUID);
        if (TxChar == null) {
            showMessage("Tx charateristic not found!");
            disconnect(address);
            return;
        }
        mBtGatt.setCharacteristicNotification(TxChar, true);

        BluetoothGattDescriptor descriptor = TxChar.getDescriptor(CCCD);
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBtGatt.writeDescriptor(descriptor);
    }

    public void enableTXNotificationAirRadio(BluetoothGatt mBtGatt, String address) {
        showMessage("enableTXNotificationAirRadio");
        if (mBtGatt == null) {
            showMessage("BluetoothGatt is null," + mBtGatt);
            disconnect(address);
            return;
        }

        BluetoothGattService RxService = mBtGatt.getService(AIR_RADIO_RX_SERVICE_UUID);
        if (RxService == null) {
            showMessage("Rx service not found!");
            disconnect(address);
            return;
        }
        BluetoothGattCharacteristic TxChar = RxService.getCharacteristic(AIR_RADIO_TX_CHAR_UUID);
        if (TxChar == null) {
            showMessage("Tx charateristic not found!");
            disconnect(address);
            return;
        }
        mBtGatt.setCharacteristicNotification(TxChar, true);

        BluetoothGattDescriptor descriptor = TxChar.getDescriptor(CCCD);
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBtGatt.writeDescriptor(descriptor);
    }

    public void changeDevState(String mac, int state) {
        DbManager db = CommonUtil.MyDbUtils();

        try {
            DeviceDb me = db.selector(DeviceDb.class).where("macAddr", "=", mac).findFirst();
            if (me != null) {
                me.setStatus(state);
                db.saveOrUpdate(me);
                devStatusEvent(me.getDevId());
                //空气站设备离线时，也发送状态改变广播到上层应用
                if(me.getType() == TYPE_AIR_RADIO && me.getStatus() == DeviceManager.Dev_STATUS_eDisconnect){
                    //发送状态改变广播到上层
                    CommonUtil.sendBroadcastMsg(mContext,ACTION_DEV_DEVCHANAGE, me.getBoxId(),
                            me.getDevId(), me.getType(),false,null);
                    Log.e(TAG,"sendBroadcastMsg===2");
                    Log.e(TAG,"======changeDevState=====name:"+me.getName()+"==已离线");
                    //Log.e(TAG,"===========changeDevState======state:"+state+"==name:"+me.getName()+"===type:"+me.getType());
                }

                Log.i(TAG, "changeDevState: " + me.getStatus() + ",mac:" + me.getMacAddr());
            } else {
                Log.i(TAG, "changeDevState: Not found, MAC:" + mac);
            }
        } catch (DbException | NumberFormatException e) {
            e.printStackTrace();
        }
    }

    private void devStatusEvent(String devId) {
       /* Intent mIntent = new Intent(ACTION_EVENT_TRIGGER);

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date curDate = new Date(System.currentTimeMillis());
        String timeStr =  formatter.format(curDate);

        mIntent.putExtra("time", timeStr);
        mIntent.putExtra("deviceId", devId);
        mIntent.putExtra("event", "EVENT");
        mIntent.putExtra("message", "Change");
        mIntent.putExtra("status", 0);

        mContext.sendBroadcast(mIntent);*/
    }

    private void showMessage(String msg) {
        Log.e(TAG, msg);
    }

    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
     *
     * @return A {@code List} of supported services.
     */
    /*public List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;

        return mBluetoothGatt.getServices();
    }*/
    public abstract void getTopBoxSensorData(String address, byte[] data);

    public abstract void getAirRadioSensorData(String address, byte[] data);

    public abstract void checkDoorStateBackCall(String mac, String record);

    public abstract void findTopBoxAddrBackCall(String address);


}
